﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ServerListerner.Connections
{
    public class UDPConnection : IUDPConnection
    {
        private bool IsClosed = false;
        private Socket _socket;
        private int bufferSize = 0;
        private int sendBufferSize = 0;
        private byte[] _buffer;
        private List<byte> _recBuffer;
        private EndPoint _remoteEndPoint;
        public Action<IUDPConnection, byte[]> OnReceived { get; set; }
        public Action<IUDPConnection, string, Exception> OnError { get; set; }

        public UDPConnection(Socket socket, Action<IUDPConnection> action) {
            this._socket = socket;
            this.bufferSize = this._socket.ReceiveBufferSize;
            this.sendBufferSize = this._socket.SendBufferSize;
            action?.Invoke(this);
            StartReceived();
        }
        ~UDPConnection()
        {
            GC.Collect();
        }
        private void StartReceived() {
            try
            {
                _remoteEndPoint=new IPEndPoint(IPAddress.Any, 0);
                this._buffer = null;
                this._buffer = new byte[this.bufferSize];
                this._socket.BeginReceiveFrom(this._buffer, 0, this.bufferSize,SocketFlags.None,ref _remoteEndPoint, Received, null);
            }
            catch (Exception ex)
            {
                OnError?.Invoke(this, "UDPStartReceived Error", ex);
            }
        }
        private void Received(IAsyncResult iar)
        {
            try
            {
                _remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
                int bytesRead = this._socket.EndReceiveFrom(iar, ref _remoteEndPoint);
                if (this._recBuffer == null)
                    this._recBuffer = new List<byte>();
                this._recBuffer.AddRange(this._buffer.Take(bytesRead));
                if (this._socket.Available<=0)
                   if(this._recBuffer!=null&&this._recBuffer.Count>0)
                    {
                        OnReceived?.Invoke(this, this._recBuffer.ToArray());
                        this._recBuffer.Clear();
                    }
            }
            catch (Exception ex)
            {
                OnError?.Invoke(this, "UDPReceived Error", ex);
            }
            StartReceived();
        }
        public void Send(EndPoint point, byte[] bytes)
        {
            var send = bytes.Take(this.sendBufferSize).ToArray();
            int len = send.Length > this.sendBufferSize ? this.sendBufferSize : send.Length;
            var data = bytes.Skip(len).ToArray();
            this._socket.BeginSendTo(send, 0, len, SocketFlags.None, point, iar =>
            {
                byte[] sendByte = (byte[])iar.AsyncState;
                this._socket.EndSendTo(iar);
                if (bytes.Length > 0)
                    this.Send(point,sendByte);
            }, data);
        }
        public void Send(IPAddress iP, int port, byte[] data) => this.Send(new IPEndPoint(iP, port), data);
        public void Send(string ip, int port, byte[] data) => this.Send(IPAddress.Parse(ip), port, data);
        public EndPoint RemoteEndPoint { get =>_remoteEndPoint; }
        public void Dispose()
        {
           this.Close();
        }
        private void Close()
        {
            this.IsClosed = true;
            this.CloseSocket();
            this.SetNull();
            GC.Collect();
        }
        private void CloseSocket()
        {
            this._socket.Shutdown(SocketShutdown.Both);
            this._socket.Close();
            this._socket.Dispose();
        }
        private void SetNull()
        {
            this._buffer = null;
            this._socket = null;
            OnReceived = null;
            OnError = null;
        }
    }
}
